package com.small.site.aspect;

import com.alibaba.fastjson.JSON;
import com.small.site.aspect.annotation.Log;
import com.small.site.constant.Constant;
import com.small.site.pojo.SysLog;
import com.small.site.service.LogService;
import com.small.site.util.IdWorker;
import com.small.site.util.JwtTokenUtil;
import com.small.site.util.R;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ForkJoinPool;

/**
 * @author xiaochi
 * @date 2021/12/15 15:33
 * @desc 日志切面
 */
@Slf4j
@Aspect
@Component
@SuppressWarnings("all")
public class AspectAdvice {
    /**
     * ForkJoinPool 多队列，在自己队列任务执行完成后，别的队列没有执行完，可以偷取别的队列的任务来执行，使用执行时间更短，效率更高
     * ThreadPoolExecutor 线程池只有一个队列，自身执行完成后，处于空闲状态
     */
    private final ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();

    @Autowired
    private IdWorker idWorker;
    @Autowired
    private LogService logService;

    @Around("execution(* com.small.site.controller..*(..))")
    public Object handleAspect(ProceedingJoinPoint joinPoint){
        long startTime = System.currentTimeMillis();
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Object[] args = joinPoint.getArgs();
        Object proceed = null;
        try {
            proceed = joinPoint.proceed(args);
            long time = System.currentTimeMillis() - startTime;
            log.info("接口地址{},请求方式{},参数{},时长{}毫秒",request.getRequestURI(),request.getMethod(),args,time);
            CompletableFuture.runAsync(() -> saveLog(joinPoint,request,args,time),forkJoinPool);
        }catch (Throwable e){
            log.error("【记录操作日志异常】，{}",e);
            return R.error(e.getMessage());
        }
        return proceed;
    }

    /**
     *保存日志
     * @param joinPoint
     * @param request
     * @param args
     * @param time
     */
    private void saveLog(ProceedingJoinPoint joinPoint,HttpServletRequest request,Object[] args,long time){
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        if (signature.getMethod().isAnnotationPresent(Log.class)){
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = signature.getName();
            SysLog sysLog = new SysLog();
            sysLog.setId(String.valueOf(idWorker.nextId()));
            sysLog.setMethod(className+"."+methodName);
            try {
                sysLog.setParams(args.length != 0 ? JSON.toJSONString(args) : "");
            }catch (Exception e){}
            Log logAnnotation = signature.getMethod().getAnnotation(Log.class);
            if (logAnnotation != null){
                sysLog.setOperation(logAnnotation.title() + "-" + logAnnotation.action());
            }
            Claims claims = JwtTokenUtil.parseToken(request.getHeader(Constant.User.ACCESS_TOKEN));
            if (claims != null){
                sysLog.setUserId((String) claims.get(Constant.User.JWT_USER_ID));
                sysLog.setUsername(claims.getSubject());
            }
            sysLog.setIp(request.getRemoteAddr());
            sysLog.setTime(time);
            sysLog.setCreateTime(new Date());
            log.info("【操作日志数据】，{}",sysLog.toString());
            logService.insert(sysLog);
        }
    }
}
